Java — 关于垃圾收集灵魂拷问三连(二)

前言

在上一篇,我们着重介绍了收集算法,这一篇是本系列的第二篇,着重讲新老年代的划分,然后顺势就介绍多个垃圾收集器。如果说收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。我们还是按照《深入理解Java虚拟机》里面使用的例子,同时我再参考一些优秀的博文来更好的阐述垃圾收集器。参考的博文会放在最后,希望我们大家都尊重原创,感谢各位的无私奉献。

再多说几句,在上一篇中,我们说到现在商业虚拟机的垃圾回收都是采用分代收集算法,而分代说的就是新生代和老年代。说到这,我们就要讲讲堆内存的分配区域。

emmmm,上次忘说了,圣诞快乐!

堆内存分配区域

堆内存的分配区域可以分为三代。分别是新生代、老年代和持久代。

  1. 新生代

    几乎所有新生成的对象首先都是放在新生代中,新生代内存按照 8:1:1 的比例分为一个Eden区和两个Survivor(Survivor0,Survivor1)区,大部分对象在Eden区生成。当新对象生成,Eden Space申请失败(因为空间不足等),则会发起一次GC。回收时先将Eden区存活的对象复制到一个Survivor0区,然后清空Eden区,当这个Survivor0区也存放满时,则将Eden区和Survivor0区存活的对象复制到两一个Survivor1区,然后清空Eden区和这个Survivor0区,此时Survivor0区是空的,然后将Survivor0区和Survivor1区交换,即保持Survivor1区为空,如此往复。当Survivor1区不足以存放Eden区和Survivor0区的存活对象时,就将对象直接存放在老年代。当对象在Survivor区躲过一次GC的话,其对象年龄就加一,默认情况下,如果对象的年龄达到十五岁,就会移动到老年代。若是老年代也满了就会触发一次Full GC,也就是新老年代都会进行回收。

  2. 老年代

    在新生代中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代中。因此,可以认为老年代中存放的都是一些生命周期较长的对象。内存比新生代也大很多,大概比例为1:2 ,当老年代内存满时触发Major GC 即 Full GC,Full GC 发生频率比较低,老年代存活时间比较长,存活率比较高。一般来说,大对象会被直接分配到老年代。所谓的大对象是指需要大量连续内存空间的对象,最常见的一种大对象就是大数组。

    当然分配的规则不是百分百固定的,这要取决于当前使用的哪种垃圾收集器组合和JVM相关参数。

  3. 持久代

    用于存放静态文件和常量等。持久代对垃圾回收没有显著地影响,对持久代的回收主要是回收两部分:废弃常量和无用的类。

    持久代空间在Java SE8特性中已经被移除,取而代之的是元空间。

垃圾收集器

可以看到一个完整的垃圾收集器其实是集合了很多收集器与一体,根据新老年代的划分而使用不同的收集算法,下面就逐个简单介绍这些收集器。

Serial 收集器

Serial 是最基本、历史最悠久的垃圾收集器,使用复制算法。串行收集器并不是只能使用一个CPU进行收集,而是当JVM需要进行垃圾回收的时候,需要中断所有用户线程,直到它回收结束,因此号称 “Stop The World”。串行回收适合低端机器,是Client模式下默认的收集器。对CPU和内存的消耗不高,适合用户交互比较少,后台任务较多的系统。

新生代、老年代使用串行回收;新生代复制算法,老年代标记-压缩。

Serial Old 收集器

SerialOld是老年代Client模式下的默认的收集器,单线程执行,使用标记-整理算法。

ParNew 收集器

ParNew 收集器其实是多线程版本的Serial收集器。它是多CPU模式下的首选回收器,该回收器在单CPU的环境下回收效率远远低于Serial收集器,所以一定要注意使用场景哦。

Parallel Scavenge 收集器

同样使用复制算法,也是一个多线程的垃圾收集器,也称吞吐量优先的收集器。

吞吐量=程序运行时间/(JVM执行回收时间+程序运行时间),假设程序运行了一百分钟,JVM的垃圾回收占用一分钟,那么吞吐量就是99%。在当今网络发达的今天,良好的响应速度是提升用户体验的一个重要指标,多核并行云计算的发展要求程序尽可能的使用CPU和内存资源,尽快的计算出最终结果,因此在交互不多的云端,比较适合使用该回收器。

Parallel Old 收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和老年代Parallet Old收集器的搭配策略。

CMS 收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以带给用户最好的用户体验。

基于标记清除算法,适用于老年代。

G1 收集器

博众家之长,吸取来增量收集的优点,把整个堆划分为一个一个等大小的区域。内存的回收和划分都以区域为单位;同时,它也汲取来CMS的特点,把垃圾回收分为几个阶段,分散一个垃圾回收过程;而且,G1也支持分代垃圾回收。为了达到对回收时间的课预计性,G1在扫描区域之后,对其中活跃对象的大小进行排序,首先会收集那些活跃度小的区域,以便快速回收空间。因为活跃对象小,里面可以认为多数都是垃圾,所以这种方式被称为Garbage First(G1)的垃圾回收算法,即:垃圾优先的回收。

最后

如果你想知道为什么复制算法中要存在两个Survivor呢?可以参见这篇文章:https://www.jianshu.com/p/a5fd5bf93d26 或者想了解更多,我建议看一下文章:

https://www.jianshu.com/p/a67c3fdcc8e8

https://www.jianshu.com/p/5261a62e4d29

我们一直都向往,面朝大海,春暖花开。 但是几人能做到,心中有爱,四季不败?